﻿using System;
using System.Runtime.InteropServices;

namespace SmartCard.PcscApi
{
	/// <summary>
	/// This is a static P/Invoke wrapper layer Win32 PCSC Smart Card API
	/// </summary>
	internal static class PcscInvokes
	{
		/// <summary>
		/// The SCardConnect function establishes a connection
		/// (using a specific resource manager context) between the
		/// calling application and a smart card contained by a specifi
		/// reader. If no card exists in the specified reader, an error is returned.
		/// </summary>
		/// <param name="hContext">A handle that identifies the resource manager context. The resource manager context is set by a previous call to SCardEstablishContext</param>
		/// <param name="szReader">The name of the reader that contains the target card.</param>
		/// <param name="dwShareMode">A flag that indicates whether other applications may form connections to the card.</param>
		/// <param name="dwPreferredProtocols">A bitmask of acceptable protocols for the connection. Possible values may be combined with the OR operation.</param>
		/// <param name="phCard">A handle that identifies the connection to the smart card in the designated reader.</param>
		/// <param name="pdwActiveProtocol">A flag that indicates the established active protocol.</param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
		public static extern int SCardConnect(
			UInt32 hContext,
			string szReader,
			UInt32 dwShareMode,
			UInt32 dwPreferredProtocols,
			out UInt32 phCard,
			out UInt32 pdwActiveProtocol);

		/// <summary>
		/// The SCardReconnect function reestablishes an existing connection
		/// between the calling application and a smart card. This function moves
		/// a card handle from direct access to general access, or acknowledges
		/// and clears an error condition that is preventing further access to the card.
		/// </summary>
		/// <param name="hCard">Reference value obtained from a previous call to SCardConnect.</param>
		/// <param name="dwShareMde">Flag that indicates whether other applications may form connections to this card.</param>
		/// <param name="dwPreferredProtocols">
		/// Bitmask of acceptable protocols for this connection. Possible values may be
		/// combined with the OR operation. The value of this parameter should include
		/// the current protocol.  Attempting to reconnect with a protocol other than
		/// the current protocol will result in an error.
		/// </param>
		/// <param name="dwInitialization">Type of initialization that should be performed on the card.</param>
		/// <param name="pdwActiveProtocol">Flag that indicates the established active protocol.</param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll", CharSet = CharSet.Unicode, BestFitMapping = false)]
		public static extern int SCardReconnect(
			UInt32 hCard,
			UInt32 dwShareMde,
			UInt32 dwPreferredProtocols,
			UInt32 dwInitialization,
			out UInt32 pdwActiveProtocol);

		/// <summary>
		/// The SCardDisconnect function terminates a connection previously opened
		/// between the calling application and a smart card in the target reader.
		/// </summary>
		/// <param name="hCard">Reference value obtained from a previous call to SCardConnect.</param>
		/// <param name="dwDisposition">Action to take on the card in the connected reader on close.</param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardDisconnect(
			UInt32 hCard,
			UInt32 dwDisposition);

		/// <summary>
		/// The SCardListReaders function provides the list of readers within a set of
		/// named reader groups, eliminating duplicates. The caller supplies a list of
		/// reader groups, and receives the list of readers within the named groups.
		/// Unrecognized group names are ignored.
		/// </summary>
		/// <param name="hContext">
		/// Handle that identifies the resource manager context for the query. The
		/// resource manager context can be set by a previous call to SCardEstablishContext.
		/// If this parameter is set to NULL, the search for readers is not
		/// limited to any context.
		/// </param>
		/// <param name="mszGroups">
		/// Names of the reader groups defined to the system, as a multi-string. Use a
		/// NULL value to list all readers in the system (that is, the SCard$AllReaders group).
		/// </param>
		/// <param name="mszReaders">
		/// Multi-string that lists the card readers within the supplied reader groups. If this
		/// value is NULL, SCardListReaders ignores the buffer length supplied in pcchReaders,
		/// writes the length of the buffer that would have been returned if this parameter had
		/// not been NULL to pcchReaders, and returns a success code.
		/// </param>
		/// <param name="pcchReaders">
		/// Length of the mszReaders buffer in characters. This parameter receives
		/// the actual length of the multi-string structure, including all trailing
		/// null characters. If the buffer length is specified as SCARD_AUTOALLOCATE,
		/// then mszReaders is converted to a pointer to a byte pointer, and
		/// receives the address of a block of memory containing the multi-string
		/// structure. This block of memory must be deallocated with SCardFreeMemory.
		/// </param>
		/// <returns> SCARD_S_SUCCESS if successful in its acquisition. </returns>
		[DllImport("winscard.dll", CharSet=CharSet.Ansi)]
		public static extern int SCardListReaders(
			UInt32 hContext,
			IntPtr mszGroups,
			byte[] mszReaders,
			ref UInt32 pcchReaders);

		/// <summary>
		/// The SCardGetStatusChange function blocks execution until the current
		/// availability of the cards in a specific set of readers changes.
		/// The caller supplies a list of readers to be monitored by an SCARD_READERSTATE
		/// array and the maximum amount of time (in milliseconds) that it is willing
		/// to wait for an action to occur on one of the listed readers. Note that
		/// SCardGetStatusChange uses the user-supplied value in the dwCurrentState
		/// members of the rgReaderStates SCARD_READERSTATE array as the definition of
		/// the current state of the readers. The function returns when there is a change
		/// in availability, having filled in the dwEventState members of rgReaderStates
		/// appropriately.
		/// </summary>
		/// <param name="hContext">
		/// A handle that identifies the resource manager context. The resource manager
		/// context is set by a previous call to the SCardEstablishContext function.
		/// </param>
		/// <param name="dwTimeout">
		/// The maximum amount of time, in milliseconds, to wait for an action. A value
		/// of zero causes the function to return immediately. A value of INFINITE
		/// causes this function never to time out.
		/// </param>
		/// <param name="rgReaderState">
		/// An array of SCARD_READERSTATE structures that specify the readers to watch, and
		/// that receives the result.
		/// 
		/// To be notified of the arrival of a new smart card reader, set the szReader member
		/// of a SCARD_READERSTATE structure to "\\\\?PnP?\\Notification", and set all of
		/// the other members of that structure to zero.
		/// 
		/// Important: Each member of each structure in this array must be initialized to
		/// zero and then set to specific values as necessary. If this is not done,
		/// the function will fail in situations that involve remote card readers.
		/// </param>
		/// <param name="cReaders">The number of elements in the rgReaderStates array.</param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll", CharSet=CharSet.Auto)]
		public static extern int SCardGetStatusChange(
			UInt32 hContext,
			UInt32 dwTimeout,
			[In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=3)]
			SCard_ReaderState[] rgReaderState,
			UInt32 cReaders);


		/// <summary>
		/// The SCardEstablishContext function establishes the resource
		/// manager context (the scope) within which database operations
		/// are performed.
		/// </summary>
		/// <param name="dwScope">
		/// Scope of the resource manager context. This parameter can be one of the
		/// following values, SCARD_SCOPE_USER, SCARD_SCOPE_SYSTEM
		/// </param>
		/// <param name="pvReserved1">
		/// Reserved for future use and must be NULL. This parameter will allow
		/// a suitably privileged management application to act on behalf of another user.
		/// </param>
		/// <param name="pvReserved2">Reserved for future use and must be NULL.</param>
		/// <param name="phContext">
		/// A handle to the established resource manager context. This handle can now
		/// be supplied to other functions attempting to do work within this context.
		/// </param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardEstablishContext(
			UInt32 dwScope,
			IntPtr pvReserved1,
			IntPtr pvReserved2,
			out UInt32 phContext);

		/// <summary>
		/// The SCardReleaseContext function closes an established resource
		/// manager context, freeing any resources allocated under that
		/// context, including SCARDHANDLE objects and memory allocated
		/// using the SCARD_AUTOALLOCATE length designator.
		/// </summary>
		/// <param name="hContext">
		/// Handle that identifies the resource manager context. The resource
		/// manager context is set by a previous call to SCardEstablishContext.
		/// </param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardReleaseContext(
			UInt32 hContext);

		/// <summary>
		/// The SCardTransmit function sends a service request to the smart card
		/// and expects to receive data back from the card.
		/// </summary>
		/// <param name="hCard">A reference value returned from the SCardConnect function.</param>
		/// <param name="pioSendPci">
		/// A pointer to the protocol header structure for the instruction. This buffer
		/// is in the format of an SCARD_IO_REQUEST structure, followed by the specific
		/// protocol control information (PCI).
		/// 
		/// For the T=0, T=1, and Raw protocols, the PCI structure is constant. The smart
		/// card subsystem supplies a global T=0, T=1, or Raw PCI structure, which you
		/// can reference by using the symbols SCARD_PCI_T0, SCARD_PCI_T1, and SCARD_PCI_RAW
		/// respectively.
		/// </param>
		/// <param name="pbSendBuffer">
		/// A pointer to the actual data to be written to the card.
		/// 
		/// For T=0, the data parameters are placed into the address pointed to by pbSendBuffer according to the following structure:
		/// 
		/// <code>
		/// struct {
		///		BYTE
		///		bCla,   // the instruction class
		///		bIns,   // the instruction code 
		///		bP1,    // parameter to the instruction
		///		bP2,    // parameter to the instruction
		///		bP3;    // size of I/O transfer
		///	} CmdBytes;
		///	</code>
		///	
		/// The data sent to the card should immediately follow the send buffer. In the special case where no data is
		/// sent to the card and no data is expected in return, bP3 is not sent.
		/// </param>
		/// <param name="cbSendLength">
		/// The length, in bytes, of the pbSendBuffer parameter.
		/// For T=0, in the special case where no data is sent to the card and no data expected
		/// in return, this length must reflect that the bP3 member is not being sent;
		/// the length should be sizeof(CmdBytes) - sizeof(BYTE).
		/// </param>
		/// <param name="pioRecvPci">
		/// Pointer to the protocol header structure for the instruction, followed by
		/// a buffer in which to receive any returned protocol control information
		/// (PCI) specific to the protocol in use. This parameter can be NULL if no
		/// PCI is returned.
		/// </param>
		/// <param name="pbRecvBuffer">
		/// Pointer to any data returned from the card.
		/// 
		/// For T=0, the data is immediately followed by the SW1 and SW2 status bytes.
		/// If no data is returned from the card, then this buffer will only contain
		/// the SW1 and SW2 status bytes.
		/// </param>
		/// <param name="pcbRecvLength">
		/// Supplies the length, in bytes, of the pbRecvBuffer parameter and receives the
		/// actual number of bytes received from the smart card. This value cannot be
		/// SCARD_AUTOALLOCATE because SCardTransmit does not support SCARD_AUTOALLOCATE.
		/// 
		/// For T=0, the receive buffer must be at least two bytes long to receive
		/// the SW1 and SW2 status bytes.
		/// </param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardTransmit(
			UInt32 hCard,
			[In] ref SCard_IO_Request pioSendPci,
			byte[] pbSendBuffer,
			UInt32 cbSendLength,
			IntPtr pioRecvPci,
			[Out] byte[] pbRecvBuffer,
			out UInt32 pcbRecvLength);

		/// <summary>
		/// The SCardBeginTransaction function starts a transaction.
		/// 
		/// The function waits for the completion of all other transactions before
		/// it begins. After the transaction starts, all other applications are
		/// blocked from accessing the smart card while the transaction is in progress.
		/// </summary>
		/// <param name="hContext">A reference value obtained from a previous call to SCardConnect.</param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		/// <remarks>
		/// This function returns SCARD_S_SUCCESS even if another process or thread has
		/// reset the card. To determine whether the card has been reset, call the
		/// SCardStatus function immediately after calling this function.
		/// </remarks>
		[DllImport("winscard.dll")]
		public static extern int SCardBeginTransaction(
			UInt32 hContext);

		/// <summary>
		/// This function completes a previously declared transaction, enabling
		/// other applications to resume interactions with the card.
		/// </summary>
		/// <param name="hContext">Handle to the reference value obtained from a previous call to SCardConnect.</param>
		/// <param name="dwDisposition">
		/// Specifies what to do with the card in the connected reader on close.
		/// The following table shows the possible values for dwDisposition. (See MSDN docs)
		/// </param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardEndTransaction(
			UInt32 hContext,
			UInt32 dwDisposition);

		/// <summary>
		/// The SCardGetAttrib function retrieves the current reader attributes
		/// for the given handle.  It does not affect the state of the reader,
		/// driver, or card.
		/// </summary>
		/// <param name="hCard">Reference value returned from SCardConnect.</param>
		/// <param name="dwAttribId">
		/// Identifier for the attribute to get. The following table lists possible
		/// values for dwAttrId. These values are read-only. Note that vendors may
		/// not support all attributes.
		/// </param>
		/// <param name="pbAttr">
		/// Pointer to a buffer that receives the attribute whose ID is supplied in dwAttrId.
		/// If this value is NULL, SCardGetAttrib ignores the buffer length supplied in
		/// pcbAttrLen, writes the length of the buffer that would have been returned if
		/// this parameter had not been NULL to pcbAttrLen, and returns a success code.
		/// </param>
		/// <param name="pcbAttrLen">
		/// Length of the pbAttr buffer in bytes, and receives the actual length of the
		/// received attribute If the buffer length is specified as SCARD_AUTOALLOCATE,
		/// then pbAttr is converted to a pointer to a byte pointer, and receives the
		/// address of a block of memory containing the attribute. This block of memory
		/// must be deallocated with SCardFreeMemory.
		/// </param>
		/// <returns>SCARD_S_SUCCESS or an error code</returns>
		[DllImport("winscard.dll")]
		public static extern int SCardGetAttrib(
			UInt32 hCard,
			UInt32 dwAttribId,
			[Out] byte[] pbAttr,
			out UInt32 pcbAttrLen);
	}

	#region PCSC Structures

	/// <summary>
	/// PC/SC Scope
	/// </summary>
	internal enum ePcscScope : uint
	{
		User = 0,
		Terminal = 1,
		System = 2
	}

	/// <summary>
	/// Share mode enumeration
	/// </summary>
	internal enum ePcscShareMode
	{
		Exclusive = 1,
		Shared = 2,
		Direct = 3
	}

	/// <summary>
	/// Protocol enumeration.  This can be inferred from the
	/// ATR code that card returns
	/// </summary>
	internal enum ePcscProtocol
	{
		Undefined = 0x00000000,
		T0 = 0x00000001,
		T1 = 0x00000002,
		Raw = 0x00010000,
		Default = unchecked((int)0x80000000),
		T0orT1 = T0 | T1
	}

	/// <summary>
	/// Disconnect action enumeration
	/// </summary>
	internal enum ePcscDisconnectDisposition
	{
		Leave = 0,
		Reset = 1,
		Unpower = 2,
		Eject = 3
	}

	/// <summary>
	/// CARD_STATE enumeration, used by the PC/SC function SCardGetStatusChanged.  Comments take
	/// from windows 7.1 SDK WinSCard.h
	/// </summary>
	[Flags]
	internal enum PcscCardState
	{
		/// <summary>
		/// The application is unaware of the current state, and would like to
		/// know.  The use of this value results in an immediate return
		/// from state transition monitoring services.  This is represented by
		/// all bits set to zero.
		/// <summary>
		Unaware = 0x00000000,

		/// <summary>
		/// The application requested that this reader be ignored.  No other	
		/// bits will be set.
		/// </summary>
		Ignore = 0x00000001,

		/// <summary>
		/// This implies that there is a difference between the state
		/// believed by the application, and the state known by the Service
		/// Manager.  When this bit is set, the application may assume a
		/// significant state change has occurred on this reader.
		/// </summary>
		Changed = 0x00000002,

		/// <summary>
		/// This implies that the given reader name is not recognized by
		/// the Service Manager.  If this bit is set, then SCARD_STATE_CHANGED
		/// and SCARD_STATE_IGNORE will also be set.
		/// </summary>
		Unknown = 0x00000004,

		/// <summary>
		// This implies that the actual state of this reader is not
		// available.  If this bit is set, then all the following bits are clear.
		/// </summary>
		Unavailiable = 0x00000008,

		/// <summary>
		/// This implies that there is not card in the reader.  If this bit
		/// is set, all the following bits will be clear.
		/// </summary>
		Empty = 0x00000010,

		/// <summary>
		// This implies that there is a card in the reader.
		/// </summary>
		Present = 0x00000020,

		/// <summary>
		/// This implies that there is a card in the reader with an ATR
		/// matching one of the target cards. If this bit is set,
		/// SCARD_STATE_PRESENT will also be set.  This bit is only returned
		/// on the SCardLocateCard() service.
		/// </summary>
		AtrMatch = 0x00000040,

		/// <summary>
		/// This implies that the card in the reader is allocated for exclusive
		/// use by another application.  If this bit is set,
		/// SCARD_STATE_PRESENT will also be set.
		/// </summary>
		Exclusive = 0x00000080,

		/// <summary>
		/// This implies that the card in th reader is in use by one or more
		/// other applications, but may be connected to in shared mode.  If
		/// this bit is set, SCARD_STATE_PRESENT will also be set.
		/// </summary>
		InUse = 0x00000100,

		/// <summary>
		/// This implies that the card in the reader is unresponsive or not
		/// supported by the reader or software.
		/// </summary>
		Mute = 0x00000200,

		/// <summary>
		/// This implies that the card in the reader has not been powered up.
		/// </summary>
		UnPowered = 0x00000400
	}

	/// <summary>
	/// Wraps the SCARD_IO_STRUCTURE
	/// </summary>
	[StructLayout(LayoutKind.Sequential)]
	public struct SCard_IO_Request
	{
		/// <summary>
		/// Protocol in use.
		/// </summary>
		public UInt32 m_dwProtocol;

		/// <summary>
		/// Length, in bytes, of the SCARD_IO_REQUEST structure plus any following PCI-specific information.
		/// </summary>
		public UInt32 m_cbPciLength;
	}

	/// <summary>
	/// Wraps theSCARD_READERSTATE structure of PC/SC
	/// </summary>
	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
	public struct SCard_ReaderState
	{
		/// <summary>
		/// use this as the m_szReader name to be notified when a new PC/SC smart card reader
		/// is introduced into the system.
		/// </summary>
		public const string NotifyNewReaderString = @"\\\\?PnP?\\Notification";

		/// <summary>
		/// A pointer to the name of the reader being monitored.
		/// Set the value of this member to "\\\\?PnP?\\Notification" and the values of all other members to zero to be notified of the arrival of a new smart card reader.
		/// </summary>
		[MarshalAs(UnmanagedType.LPTStr)]
		public string m_szReader;

		/// <summary>
		/// Not used by the smart card subsystem. This member is used by the application.
		/// </summary>
		public IntPtr m_pvUserData;

		/// <summary>
		/// Current state of the reader, as seen by the application. This field can take on any of the following values, in combination, as a bitmask. 
		/// </summary>
		public UInt32 m_dwCurrentState;

		/// <summary>
		/// Current state of the reader, as known by the smart card resource manager. This field can take on any of the following values, in combination, as a bitmask. 
		/// </summary>
		public UInt32 m_dwEventState;

		/// <summary>
		/// Number of bytes in the returned ATR.
		/// </summary>
		public UInt32 m_cbAtr;

		/// <summary>
		/// ATR of the inserted card, with extra alignment bytes.
		/// </summary>
		[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
		public byte[] m_rgbAtr;
	}

	#endregion
}
